/* bootstrap.c -- HENlo bootstrap menu
 *
 * Copyright (C) 2019 TheFloW
 *
 * This software may be modified and distributed under the terms
 * of the MIT license.  See the LICENSE file for details.
 */

#include <vitasdk.h>

#include "psvdebug.h"
#include "config.h"
#include "utils.h"

#define WHITE 0xFFFFFFFF
#define GREEN 0xFF00FF00

#define printf psvDebugScreenPrintf

#define VITASHELL_BASE_ADDRESS "https://raw.githubusercontent.com/TheOfficialFloW/VitaShell/master/release"

#define INCLUDE_EXTERN_RESOURCE(name)                \
    extern unsigned char _binary_res_##name##_start; \
    extern unsigned char _binary_res_##name##_size;

INCLUDE_EXTERN_RESOURCE(taihen_skprx);
INCLUDE_EXTERN_RESOURCE(henkaku_skprx);
INCLUDE_EXTERN_RESOURCE(henkaku_suprx);

enum Items
{
    EXIT,
    INSTALL_HENKAKU,
    DOWNLOAD_VITASHELL,
    INSTALL_GAMESD,
    RESET_TAIHEN_CONFIG
};

const char *items[] = {
    "退出",
    "安装HENkaku",
    "下载VitaShell",
    "安装gamesd.skprx",
    "重置taiHEN config.txt",
};

#define N_ITEMS (sizeof(items) / sizeof(char *))

int __attribute__((naked, noinline)) call_syscall(int a1, int a2, int a3, int num)
{
    __asm__(
        "mov r12, %0 \n"
        "svc 0 \n"
        "bx lr \n"
        :
        : "r"(num));
}

int load_sce_paf()
{
    static uint32_t argp[] = {0x400000, 0xEA60, 0x40000, 0, 0};

    int result = -1;

    uint32_t buf[4];
    buf[0] = sizeof(buf);
    buf[1] = (uint32_t)&result;
    buf[2] = -1;
    buf[3] = -1;

    return sceSysmoduleLoadModuleInternalWithArg(SCE_SYSMODULE_INTERNAL_PAF, sizeof(argp), argp, buf);
}

int unload_sce_paf()
{
    uint32_t buf = 0;
    return sceSysmoduleUnloadModuleInternalWithArg(SCE_SYSMODULE_INTERNAL_PAF, 0, NULL, &buf);
}

int promote_app(const char *path)
{
    int res;

    load_sce_paf();

    res = sceSysmoduleLoadModuleInternal(SCE_SYSMODULE_INTERNAL_PROMOTER_UTIL);
    if (res < 0)
        return res;

    res = scePromoterUtilityInit();
    if (res < 0)
        return res;

    res = scePromoterUtilityPromotePkgWithRif(path, 1);
    if (res < 0)
        return res;

    res = scePromoterUtilityExit();
    if (res < 0)
        return res;

    res = sceSysmoduleUnloadModuleInternal(SCE_SYSMODULE_INTERNAL_PROMOTER_UTIL);
    if (res < 0)
        return res;

    unload_sce_paf();

    return res;
}

int download(const char *src, const char *dst)
{
    int ret;
    int statusCode;
    int tmplId = -1, connId = -1, reqId = -1;
    SceUID fd = -1;

    ret = sceHttpCreateTemplate("HENlo/1.00 libhttp/1.1", SCE_HTTP_VERSION_1_1, SCE_TRUE);
    if (ret < 0)
        goto ERROR_EXIT;

    tmplId = ret;

    ret = sceHttpCreateConnectionWithURL(tmplId, src, SCE_TRUE);
    if (ret < 0)
        goto ERROR_EXIT;

    connId = ret;

    ret = sceHttpCreateRequestWithURL(connId, SCE_HTTP_METHOD_GET, src, 0);
    if (ret < 0)
        goto ERROR_EXIT;

    reqId = ret;

    ret = sceHttpSendRequest(reqId, NULL, 0);
    if (ret < 0)
        goto ERROR_EXIT;

    ret = sceHttpGetStatusCode(reqId, &statusCode);
    if (ret < 0)
        goto ERROR_EXIT;

    if (statusCode == 200)
    {
        uint8_t buf[4096];
        uint64_t size = 0;
        uint32_t value = 0;

        ret = sceHttpGetResponseContentLength(reqId, &size);
        if (ret < 0)
            goto ERROR_EXIT;

        ret = sceIoOpen(dst, SCE_O_WRONLY | SCE_O_CREAT | SCE_O_TRUNC, 6);
        if (ret < 0)
            goto ERROR_EXIT;

        fd = ret;

        int x = psvDebugScreenGetX();
        int y = psvDebugScreenGetY();

        while (1)
        {
            int read = sceHttpReadData(reqId, buf, sizeof(buf));

            if (read < 0)
            {
                ret = read;
                break;
            }

            if (read == 0)
                break;

            int written = sceIoWrite(fd, buf, read);

            if (written < 0)
            {
                ret = written;
                break;
            }

            value += read;

            printf("%d%%", (int)((value * 100) / (uint32_t)size));
            psvDebugScreenSetXY(x, y);
        }
    }

ERROR_EXIT:
    if (fd >= 0)
        sceIoClose(fd);

    if (reqId >= 0)
        sceHttpDeleteRequest(reqId);

    if (connId >= 0)
        sceHttpDeleteConnection(connId);

    if (tmplId >= 0)
        sceHttpDeleteTemplate(tmplId);

    return ret;
}

void init_net()
{
    static char memory[16 * 1024];

    sceSysmoduleLoadModule(SCE_SYSMODULE_NET);
    sceSysmoduleLoadModule(SCE_SYSMODULE_HTTPS);

    SceNetInitParam param;
    param.memory = memory;
    param.size = sizeof(memory);
    param.flags = 0;

    sceNetInit(&param);
    sceNetCtlInit();

    sceSslInit(300 * 1024);
    sceHttpInit(40 * 1024);

    sceHttpsDisableOption(SCE_HTTPS_FLAG_SERVER_VERIFY);
}

void finish_net()
{
    sceSslTerm();
    sceHttpTerm();
    sceNetCtlTerm();
    sceNetTerm();
    sceSysmoduleUnloadModule(SCE_SYSMODULE_HTTPS);
    sceSysmoduleUnloadModule(SCE_SYSMODULE_NET);
}

typedef struct
{
    char *src;
    char *dst;
} DownloadSrcDst;

DownloadSrcDst vitashell_src_dst[] = {
    {"eboot.bin", "ux0:temp/app/eboot.bin"},
    {"icon0.png", "ux0:temp/app/sce_sys/icon0.png"},
    {"param.sfo", "ux0:temp/app/sce_sys/param.sfo"},
    {"bg.png", "ux0:temp/app/sce_sys/livearea/contents/bg.png"},
    {"startup.png", "ux0:temp/app/sce_sys/livearea/contents/startup.png"},
    {"template.xml", "ux0:temp/app/sce_sys/livearea/contents/template.xml"},
    {"head.bin", "ux0:temp/app/sce_sys/package/head.bin"},
};

int download_vitashell()
{
    char url[256];
    int res;
    int i;

    init_net();

    remove_file("ux0:patch/VITASHELL");

    sceIoMkdir("ux0:VitaShell", 0777);
    sceIoMkdir("ux0:temp", 6);
    sceIoMkdir("ux0:temp/app", 6);
    sceIoMkdir("ux0:temp/app/sce_sys", 6);
    sceIoMkdir("ux0:temp/app/sce_sys/livearea", 6);
    sceIoMkdir("ux0:temp/app/sce_sys/livearea/contents", 6);
    sceIoMkdir("ux0:temp/app/sce_sys/package", 6);

    for (i = 0; i < sizeof(vitashell_src_dst) / sizeof(DownloadSrcDst); i++)
    {
        printf(" > 正在下载 %s...", vitashell_src_dst[i].src);
        sceClibSnprintf(url, sizeof(url), "%s/%s", VITASHELL_BASE_ADDRESS, vitashell_src_dst[i].src);
        res = download(url, vitashell_src_dst[i].dst);
        printf("\n");
        if (res < 0)
            return res;
    }

    printf(" > 正在安装应用...\n");
    res = promote_app("ux0:temp/app");
    if (res < 0)
        return res;

    finish_net();

    return 0;
}

int install_henkaku()
{
    int res;

    sceIoMkdir("ur0:tai", 6);

    res = write_file("ur0:tai/taihen.skprx", (void *)&_binary_res_taihen_skprx_start, (int)&_binary_res_taihen_skprx_size);
    if (res < 0)
        return res;
    res = write_file("ur0:tai/henkaku.skprx", (void *)&_binary_res_henkaku_skprx_start, (int)&_binary_res_henkaku_skprx_size);
    if (res < 0)
        return res;
    res = write_file("ur0:tai/henkaku.suprx", (void *)&_binary_res_henkaku_suprx_start, (int)&_binary_res_henkaku_suprx_size);
    if (res < 0)
        return res;

    return 0;
}

int enter_cross = 0;
uint32_t old_buttons = 0, current_buttons = 0, pressed_buttons = 0;

void read_pad(void)
{
    SceCtrlData pad;
    sceCtrlPeekBufferPositive(0, &pad, 1);

    old_buttons = current_buttons;
    current_buttons = pad.buttons;
    pressed_buttons = current_buttons & ~old_buttons;
}

int wait_confirm(const char *msg)
{
    printf(msg);
    printf(" > 按%c确认，或按%c拒绝.\n", enter_cross ? 'X' : 'O', enter_cross ? 'O' : 'X');

    while (1)
    {
        read_pad();

        if ((enter_cross && pressed_buttons & SCE_CTRL_CROSS) ||
            (!enter_cross && pressed_buttons & SCE_CTRL_CIRCLE))
        {
            return 1;
        }

        if ((enter_cross && pressed_buttons & SCE_CTRL_CIRCLE) ||
            (!enter_cross && pressed_buttons & SCE_CTRL_CROSS))
        {
            return 0;
        }

        sceKernelDelayThread(10 * 1000);
    }

    return 0;
}

void print_result(int res)
{
    if (res < 0)
        printf(" > 失败! 0x%08X\n", res);
    else
        printf(" > 完成!\n");
    sceKernelDelayThread((res < 0) ? (5 * 1000 * 1000) : (1 * 1000 * 1000));
}

int print_menu(int sel)
{
    int i;

    psvDebugScreenSetXY(0, 0);
    psvDebugScreenSetTextColor(GREEN);
    printf("\n HENlo引导程序菜单\n\n");

    for (i = 0; i < N_ITEMS; i++)
    {
        psvDebugScreenSetTextColor(sel == i ? GREEN : WHITE);
        printf(" [%c] %s\n", sel == i ? '*' : ' ', items[i]);
    }

    printf("\n");

    psvDebugScreenSetTextColor(GREEN);
    printf("----------------------------\n\n");

    return 0;
}

void _start() __attribute__((weak, alias("module_start")));
int module_start(SceSize args, void *argp)
{
    SceAppUtilInitParam init_param;
    SceAppUtilBootParam boot_param;
    int syscall_id;
    int enter_button;
    int sel;
    int res;

    syscall_id = *(uint16_t *)argp;

    sceAppMgrDestroyOtherApp();

    sceShellUtilInitEvents(0);
    sceShellUtilLock(SCE_SHELL_UTIL_LOCK_TYPE_PS_BTN);

    sceClibMemset(&init_param, 0, sizeof(SceAppUtilInitParam));
    sceClibMemset(&boot_param, 0, sizeof(SceAppUtilBootParam));
    sceAppUtilInit(&init_param, &boot_param);

    sceAppUtilSystemParamGetInt(SCE_SYSTEM_PARAM_ID_ENTER_BUTTON, &enter_button);
    enter_cross = enter_button == SCE_SYSTEM_PARAM_ENTER_BUTTON_CROSS;

    psvDebugScreenInit();

    sel = 0;
    print_menu(sel);

    while (1)
    {
        read_pad();

        if (pressed_buttons & SCE_CTRL_UP)
        {
            if (sel > 0)
                sel--;
            else
                sel = N_ITEMS - 1;

            print_menu(sel);
        }

        if (pressed_buttons & SCE_CTRL_DOWN)
        {
            if (sel < N_ITEMS - 1)
                sel++;
            else
                sel = 0;

            print_menu(sel);
        }

        if ((enter_cross && pressed_buttons & SCE_CTRL_CROSS) ||
            (!enter_cross && pressed_buttons & SCE_CTRL_CIRCLE))
        {
            psvDebugScreenSetTextColor(GREEN);

            if (sel == EXIT)
            {
                printf(" > 正在退出...\n");
                sceKernelDelayThread(500 * 1000);
                break;
            }
            else if (sel == INSTALL_HENKAKU)
            {
                printf(" > 正在安装HENkaku...\n");
                sceKernelDelayThread(500 * 1000);
                res = install_henkaku();
            }
            else if (sel == DOWNLOAD_VITASHELL)
            {
                printf(" > 正在下载VitaShell...\n");
                sceKernelDelayThread(500 * 1000);
                res = download_vitashell();
            }
            else if (sel == INSTALL_GAMESD)
            {
                printf(" > 正在安装gamesd.skprx...\n");
                sceKernelDelayThread(500 * 1000);
                res = install_gamesd_plugin();
            }
            else if (sel == RESET_TAIHEN_CONFIG)
            {
                if (wait_confirm(" > 确定要重置taiHEN config.txt？\n"))
                {
                    printf(" > 正在重置taiHEN config.txt...\n");
                    sceKernelDelayThread(500 * 1000);
                    res = reset_taihen_config();
                }
                else
                {
                    sel = 0;
                    psvDebugScreenClear();
                    print_menu(sel);
                    continue;
                }
            }

            print_result(res);

            sel = 0;
            psvDebugScreenClear();
            print_menu(sel);
        }

        sceKernelDelayThread(10 * 1000);
    }

    sceShellUtilUnlock(SCE_SHELL_UTIL_LOCK_TYPE_PS_BTN);

    // Install HENkaku if any of the modules are missing
    if (!file_exists("ur0:tai/henkaku.suprx") ||
        !file_exists("ur0:tai/henkaku.skprx") ||
        !file_exists("ur0:tai/taihen.skprx"))
    {
        printf(" > 正在安装HENkaku...\n");
        sceKernelDelayThread(500 * 1000);
        res = install_henkaku();
        print_result(res);
    }

    // Write taiHEN configs if both at ur0: and ux0: don't exist
    if (!file_exists("ur0:tai/config.txt") &&
        !file_exists("ux0:tai/config.txt"))
    {
        printf(" > 正在写入taiHEN config.txt...\n");
        sceKernelDelayThread(500 * 1000);
        res = reset_taihen_config();
        print_result(res);
    }

    // Remove pkg patches
    res = call_syscall(0, 0, 0, syscall_id + 1);

    if (res >= 0)
    {
        // Start HENkaku
        res = call_syscall(0, 0, 0, syscall_id + 0);
    }
    else
    {
        // Remove sig patches
        call_syscall(0, 0, 0, syscall_id + 2);
    }

    // Clean up
    call_syscall(0, 0, 0, syscall_id + 3);

    if (res < 0 && res != 0x8002D013 && res != 0x8002D017)
    {
        printf(" > 加载HENkaku失败! 0x%08X\n", res);
        printf(" > 请重新启动HENlo并选择'安装HENkaku'.\n");
        sceKernelDelayThread(5 * 1000 * 1000);
    }

    sceKernelExitProcess(0);
    return 0;
}
